package org.codehaus.activemq.filter;

import java.io.PrintStream;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jms.JMSException;
import javax.jms.Message;

public abstract class ComparisonExpression extends BinaryExpression
  implements BooleanExpression
{
  private static final HashSet REGEXP_CONTROL_CHARS = new HashSet();

  public static BooleanExpression createBetween(Expression value, Expression left, Expression right)
  {
    return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right));
  }

  public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) {
    return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right));
  }

  public static BooleanExpression createLike(Expression left, String right, String escape)
  {
    if ((escape != null) && (escape.length() != 1)) {
      throw new RuntimeException("The ESCAPE string litteral is invalid.  It can only be one character.  Litteral used: " + escape);
    }
    int c = -1;
    if (escape != null) {
      c = 0xFFFF & escape.charAt(0);
    }

    return new LikeExpression(left, right, c);
  }

  public static BooleanExpression createNotLike(Expression left, String right, String escape) {
    return UnaryExpression.createNOT(createLike(left, right, escape));
  }

  public static BooleanExpression createInFilter(Expression left, List elements) {
    if (elements.isEmpty()) {
      return ConstantExpression.FALSE;
    }

    BooleanExpression answer = createEqual(left, (Expression)elements.get(0));
    for (int i = 1; i < elements.size(); i++) {
      answer = LogicExpression.createOR(answer, createEqual(left, (Expression)elements.get(i)));
    }
    return answer;
  }

  public static BooleanExpression createNotInFilter(Expression left, List elements) {
    if (elements.isEmpty()) {
      return ConstantExpression.TRUE;
    }

    BooleanExpression answer = createEqual(left, (Expression)elements.get(0));
    for (int i = 1; i < elements.size(); i++) {
      answer = LogicExpression.createOR(answer, createEqual(left, (Expression)elements.get(i)));
    }
    return UnaryExpression.createNOT(answer);
  }

  public static BooleanExpression createIsNull(Expression left) {
    return createEqual(left, ConstantExpression.NULL);
  }

  public static BooleanExpression createIsNotNull(Expression left) {
    return createNotEqual(left, ConstantExpression.NULL);
  }

  public static BooleanExpression createNotEqual(Expression left, Expression right) {
    return UnaryExpression.createNOT(createEqual(left, right));
  }

  public static BooleanExpression createEqual(Expression left, Expression right) {
    return new ComparisonExpression(left, right)
    {
      public Object evaluate(Message message) throws JMSException {
        Object obj1 = this.left.evaluate(message);
        Object obj2 = this.right.evaluate(message);

        Comparable lv = (obj1 instanceof Comparable) ? (Comparable)obj1 : null;
        Comparable rv = (obj2 instanceof Comparable) ? (Comparable)obj2 : null;

        if (((lv == null ? 1 : 0) ^ (rv == null ? 1 : 0)) != 0) {
          return Boolean.FALSE;
        }
        if (lv == rv) {
          return Boolean.TRUE;
        }
        return compare(lv, rv);
      }

      protected boolean asBoolean(int answer) {
        return answer == 0;
      }

      public String getExpressionSymbol() {
        return "=";
      } } ;
  }

  public static BooleanExpression createGreaterThan(Expression left, Expression right) {
    return new ComparisonExpression(left, right) {
      protected boolean asBoolean(int answer) {
        return answer > 0;
      }

      public String getExpressionSymbol() {
        return ">";
      } } ;
  }

  public static BooleanExpression createGreaterThanEqual(Expression left, Expression right) {
    return new ComparisonExpression(left, right) {
      protected boolean asBoolean(int answer) {
        return answer >= 0;
      }

      public String getExpressionSymbol() {
        return ">=";
      } } ;
  }

  public static BooleanExpression createLessThan(Expression left, Expression right) {
    return new ComparisonExpression(left, right)
    {
      protected boolean asBoolean(int answer) {
        return answer < 0;
      }

      public String getExpressionSymbol() {
        return "<";
      }
    };
  }

  public static BooleanExpression createLessThanEqual(Expression left, Expression right) {
    return new ComparisonExpression(left, right)
    {
      protected boolean asBoolean(int answer) {
        return answer <= 0;
      }

      public String getExpressionSymbol() {
        return "<=";
      }
    };
  }

  public ComparisonExpression(Expression left, Expression right)
  {
    super(left, right);
  }

  public Object evaluate(Message message) throws JMSException {
    Comparable lv = (Comparable)this.left.evaluate(message);
    if (lv == null) {
      return null;
    }
    Comparable rv = (Comparable)this.right.evaluate(message);
    if (rv == null) {
      return null;
    }
    return compare(lv, rv);
  }

  protected Boolean compare(Comparable lv, Comparable rv) {
    Class lc = lv.getClass();
    Class rc = rv.getClass();

    if (lc != rc) {
      if (lc == Integer.class) {
        if (rc == Long.class) {
          lv = new Long(((Integer)lv).longValue());
        }
        else if (rc == Double.class) {
          lv = new Double(((Long)lv).doubleValue());
        }
        else {
          throw new RuntimeException("You cannot compare a '" + lc.getName() + "' and a '" + rc.getName() + "'");
        }
      }

      if (lc == Long.class) {
        if (rc == Integer.class) {
          rv = new Long(((Integer)rv).longValue());
        }
        else if (rc == Double.class) {
          lv = new Double(((Long)lv).doubleValue());
        }
        else {
          throw new RuntimeException("You cannot compare a '" + lc.getName() + "' and a '" + rc.getName() + "'");
        }
      }

      if (lc == Double.class) {
        if (rc == Integer.class) {
          rv = new Double(((Integer)rv).doubleValue());
        }
        else if (rc == Long.class) {
          rv = new Double(((Long)rv).doubleValue());
        }
        else {
          throw new RuntimeException("You cannot compare a '" + lc.getName() + "' and a '" + rc.getName() + "'");
        }
      }
    }

    return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
  }

  protected abstract boolean asBoolean(int paramInt);

  static
  {
    REGEXP_CONTROL_CHARS.add(new Character('.'));
    REGEXP_CONTROL_CHARS.add(new Character('\\'));
    REGEXP_CONTROL_CHARS.add(new Character('['));
    REGEXP_CONTROL_CHARS.add(new Character(']'));
    REGEXP_CONTROL_CHARS.add(new Character('^'));
    REGEXP_CONTROL_CHARS.add(new Character('$'));
    REGEXP_CONTROL_CHARS.add(new Character('?'));
    REGEXP_CONTROL_CHARS.add(new Character('*'));
    REGEXP_CONTROL_CHARS.add(new Character('+'));
    REGEXP_CONTROL_CHARS.add(new Character('{'));
    REGEXP_CONTROL_CHARS.add(new Character('}'));
    REGEXP_CONTROL_CHARS.add(new Character('|'));
    REGEXP_CONTROL_CHARS.add(new Character('('));
    REGEXP_CONTROL_CHARS.add(new Character(')'));
    REGEXP_CONTROL_CHARS.add(new Character(':'));
    REGEXP_CONTROL_CHARS.add(new Character('&'));
    REGEXP_CONTROL_CHARS.add(new Character('<'));
    REGEXP_CONTROL_CHARS.add(new Character('>'));
    REGEXP_CONTROL_CHARS.add(new Character('='));
    REGEXP_CONTROL_CHARS.add(new Character('!'));
  }

  static class LikeExpression extends UnaryExpression
    implements BooleanExpression
  {
    Pattern likePattern;

    public LikeExpression(Expression right, String like, int escape)
    {
      super(right);

      StringBuffer regexp = new StringBuffer(like.length() * 2);
      regexp.append("\\A");
      for (int i = 0; i < like.length(); i++) {
        char c = like.charAt(i);
        if (escape == (0xFFFF & c)) {
          i++;
          if (i >= like.length())
          {
            break;
          }

          char t = like.charAt(i);
          regexp.append("\\x");
          regexp.append(Integer.toHexString(0xFFFF & t));
        }
        else if (c == '%') {
          regexp.append(".*?");
        }
        else if (c == '_') {
          regexp.append(".");
        }
        else if (ComparisonExpression.REGEXP_CONTROL_CHARS.contains(new Character(c))) {
          regexp.append("\\x");
          regexp.append(Integer.toHexString(0xFFFF & c));
        }
        else {
          regexp.append(c);
        }
      }
      regexp.append("\\z");

      System.out.println("regexp: " + like + ": " + regexp);
      this.likePattern = Pattern.compile(regexp.toString(), 32);
    }

    public String getExpressionSymbol()
    {
      return "LIKE";
    }

    public Object evaluate(Message message)
      throws JMSException
    {
      Object rv = getRight().evaluate(message);

      if (rv == null) {
        return null;
      }

      if (!(rv instanceof String)) {
        throw new RuntimeException("LIKE can only operate on String identifiers.  LIKE attemped on: '" + rv.getClass());
      }

      return this.likePattern.matcher((String)rv).matches() ? Boolean.TRUE : Boolean.FALSE;
    }
  }
}